package com.dopplr.api
{    
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.events.StatusEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;

    [Event(name="status", type="flash.events.StatusEvent")]
    [Event(name="error",  type="flash.events.ErrorEvent")]
    /**
     * @see http://dopplr.pbwiki.com/API+Resource+URLs
     * 
     * <p>Traveller methods implemented:</p>
     * <ul>
     *   <li>fellows (only for current authenticated user)</li>
     *   <li>traveller_info</li> 
     *   <li>trips_info</li>
     *   <li>fellows_travellingtoday (only for current authenticated user)</li>
     * </ul>
     * 
     * <p>TODO:</p>
     * <ul> 
     *   <li>Traveller methods: tag, location_on_date</li>
     *   <li>Trip methods (called as /api/methodname?trip_id=<trip id>): trip_info, add_trip_tags, add_trip_note delete_trip</li>
     *   <li>City methods (called as /api/methodname?geoname_id=<geoname id>): city_info, add_trip</li>
     *   <li>Search methods: search city_search traveller_search</li>
     *   <li>Other things: update_traveller </li>
     * </ul>
     */
    public class API extends EventDispatcher
    {
        /** Dopplr API endpoint */
        public static const API_URL:String = "https://www.dopplr.com/api/";
        
        /** AuthSub token, normally passed in via flashvars */
        private var token:String;
        
        /** Parses XML and keeps track of what Traveller, Trip and City objects we've already loaded */
        public var model:Model;

        /** Create a new API object for accessing Dopplr.  
         * 
         * <p>You can obtain a token for testing purposes 
         * from <a href="http://www.dopplr.com/account/api_session_token">your Dopplr account page</a> 
         * but in practice you'll need your users to obtain one 
         * for their account, which you can do using javascript 
         * (<a href="http://www.hackdiary.com/dopplr/apitest.html">Matt wrote an example</a>).</p>
         * 
         * @param token an AuthSub token
         * 
         * @see http://www.dopplr.com/account/api_session_token
         * @see http://www.hackdiary.com/dopplr/apitest.html
         */
        public function API(token:String)
        {
            //Security.loadPolicyFile("http://dopplr.com/api/crossdomain.xml");
            this.token = token;
            this.model = new Model();
        }

        //
        // API status stuff
        //
        
        /** dispatches ErrorEvents */
        private function error(text:String):void
        {
            // NB:- it seems to be a bad thing to throw ErrorEvents if you don't add a listener...
            dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, text));
        }
        
        /** dispatches StatusEvents */
        private function status(text:String):void
        {
            dispatchEvent(new StatusEvent(StatusEvent.STATUS, false, false, text, "status"));
        }

        
        //
        // CURRENT TRAVELLER, OR ANY TRAVELLER
        //
        
        /** load traveller_info for the currently authenticated user 
         *  @see http://dopplr.pbwiki.com/method:traveller_info */
        public function loadAuthenticatedUser(callback:Function):void
        {
            loadTravellerInfo(null, callback);
        }
        
        /** load traveller_info for the traveller with the given nickname, 
         *  if nick is null load the current authenticated user
         *  if the traveller already exists, just update their info with home city and current city
         *  @see http://dopplr.pbwiki.com/method:traveller_info */
        public function loadTravellerInfo(nick:String, callback:Function):void
        {
            var url:String = API_URL+"traveller_info?token="+token;
            if (nick) {
                status("loading information for " + nick);
                url += "&traveller="+nick;
            }
            else {
                status("loading your account information");
            }
            trace(url);
            var request:URLRequest = new URLRequest(url);
            var loader:URLLoader = new URLLoader(request);
            loader.addEventListener(Event.COMPLETE, function (event:Event):void {
                var traveller_info:XML = XML(loader.data);
                if (traveller_info.error[0]) {
                    error(traveller_info.error.text());
                }
                else {
                    var traveller:Traveller = model.getTravellerfromXML(traveller_info.traveller[0]);
                    status("loaded information for " + traveller.name);
                    callback(traveller);
                }
            }, false, 0, true);
            var onInfoError:Function = function(event:Event):void {
                error("error loading information for " + nick);
            };
            loader.addEventListener(IOErrorEvent.IO_ERROR, onInfoError, false, 0, true);
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onInfoError, false, 0, true);        
        }
        
        //
        // LOAD TRIPS FOR ONE TRAVELLER
        //
         
        /** load trips_info for the given user 
         *  @see http://dopplr.pbwiki.com/method:trips_info */
        public function loadTravellerTrips(traveller:Traveller, callback:Function):void
        {
            status("loading trips for " + traveller.name);
            var url:String = API_URL+"trips_info?token="+token+"&traveller="+traveller.nick;
            var request:URLRequest = new URLRequest(url);
            var loader:URLLoader = new URLLoader(request);
            loader.addEventListener(Event.COMPLETE, function onTripsLoaded(event:Event):void {
                status("loaded trips for " + traveller.name);
                var trips_info:XML = XML(loader.data);
                for each (var trip:XML in trips_info.trip) {
                    traveller.trips.push(model.getTripFromXML(trip));
                }
                callback(traveller);
                // TODO: error handling ?
            }, false, 0, true);
            var onTripsError:Function = function(event:Event):void {
                error("error loading trips for " + traveller.name);
            };
            loader.addEventListener(IOErrorEvent.IO_ERROR, onTripsError, false, 0, true);
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onTripsError, false, 0, true);
        }

        //
        // CURRENT TRAVELLER'S FELLOW TRAVELLERS (WITH TRIPS)
        //
        
        /** Load the currently authenticated user's fellow travellers. 
         * NB:- they don't come with trips so use loadTripsForFellows to get them
         * NB:- they also don't come with home city or current city so use loadFellowTravellersInfo to get those  */
        public function loadFellowTravellers(callback:Function):void
        {
            // TODO: allow loading of another traveller's fellows
            status("loading your fellow travellers");
            var url:String = API_URL+"fellows?token="+token;
            var request:URLRequest = new URLRequest(url);
            var loader:URLLoader= new URLLoader(request);
            loader.addEventListener(Event.COMPLETE, function(event:Event):void {
                status("loaded fellow travellers");
                var fellows:XML = XML(loader.data);
                var users:Array = [];
                for each (var user:XML in fellows.can_see_trips_of) {
                    users.push(model.getTravellerfromXML(user));
                }
                callback(users);
                // TODO handle errors in data
                // TODO handle shows_trips_to?
            }, false, 0, true);
            var onFellowsError:Function = function(event:Event):void {
                error("error loading fellow travellers!");
            };
            loader.addEventListener(IOErrorEvent.IO_ERROR, onFellowsError, false, 0, true);
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onFellowsError, false, 0, true);
        }
        
        //
        // FELLOWS TRAVELLING TODAY
        //

        /** Get a list of the current authenticated user's fellow travellers who are travelling today.
         *  callback will receive an Array of Travellers and their currentTrip should be set. */
        public function loadFellowsTravellingToday(callback:Function):void
        {
            // TODO: allow loading of another traveller's fellows travellingtoday - if that makes sense?
            status("loading your fellows travelling today");
            var url:String = API_URL+"fellows_travellingtoday?token="+token;
            var request:URLRequest = new URLRequest(url);
            var loader:URLLoader= new URLLoader(request);
            loader.addEventListener(Event.COMPLETE, function(event:Event):void {
                status("loaded your fellows travelling today");
                var fellows_travellingtoday:XML = XML(loader.data);
                var users:Array = [];
                for each (var user:XML in fellows_travellingtoday.travelling) {
                    users.push(model.getTravellerfromXML(user));
                }
                callback(users);
                // TODO handle errors in data
            }, false, 0, true);
            var onFellowsError:Function = function(event:Event):void {
                error("error loading fellows travelling today");
            };
            loader.addEventListener(IOErrorEvent.IO_ERROR, onFellowsError, false, 0, true);
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onFellowsError, false, 0, true);
        }

        //
        // LOAD TRIPS FOR A LIST OF USERS
        //
        
        /** load trip info for the given array of users (must be fellow travellers of the currently authenticated traveller) */
        public function loadFellowTravellersTrips(fellows:Array, callback:Function):void
        {
            loadTripsInfoForTravellerArray(fellows, callback, 0);
        }
        
        private function loadTripsInfoForTravellerArray(travellers:Array, callback:Function, index:int):void
        {
            if (index < travellers.length) {
                var traveller:Traveller = travellers[index] as Traveller;
                if (traveller.trips.length == 0) {
                    status("loading trips for traveller " + (index+1) + " of " + travellers.length + ", " + traveller.name);
                    var url:String = API_URL+"trips_info?token="+token+"&traveller="+traveller.nick;
                    var request:URLRequest = new URLRequest(url);
                    var loader:URLLoader = new URLLoader(request);
                    loader.addEventListener(Event.COMPLETE, function (event:Event):void {
                        status("loaded trips for fellow traveller " + traveller.name);
                        var trips_info:XML = XML(loader.data);
                        for each (var trip:XML in trips_info.trip) {
                            var t:Trip = model.getTripFromXML(trip);
                            //trace(t.city + " on " + t.start.toUTCString());
                            traveller.trips.push(t);
                        }
                        loadTripsInfoForTravellerArray(travellers, callback, index+1);
                    }, false, 0, true);
                    var onUserTripsError:Function = function(event:Event):void {
                        error("error loading trips for " + traveller.name);
                        // TODO dispatch error? retry?
                        loadTripsInfoForTravellerArray(travellers, callback, index+1);
                    };
                    loader.addEventListener(IOErrorEvent.IO_ERROR, onUserTripsError, false, 0, true);
                    loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onUserTripsError, false, 0, true);
                }
                else {
                    status("already got trips for fellow traveller " + traveller.name);
                    loadTripsInfoForTravellerArray(travellers, callback, index+1);
                }
            }
            else {
                callback(travellers);
            }
        }
        
        //
        // LOAD INFO (HOME CITY AND CURRENT CITY) FOR A GIVEN LIST OF TRAVELLERS
        //

        /** update the given array of travellers with home city and current city info from /api/traveller_info */
        public function loadFellowTravellersInfo(travellers:Array, callback:Function):void
        {
            loadFellowTravellersInfoArray(travellers, callback, 0);
        }
        
        private function loadFellowTravellersInfoArray(travellers:Array, callback:Function, index:int):void
        {
            if (index < travellers.length) {
                var traveller:Traveller = travellers[index] as Traveller;
                status("loading info for traveller " + (index+1) + " of " + travellers.length + ", " + traveller.name);
                if (!traveller.homeCity) {
                    var url:String = API_URL+"traveller_info?token="+token+"&traveller="+traveller.nick;
                    var request:URLRequest = new URLRequest(url);
                    var loader:URLLoader = new URLLoader(request);
                    loader.addEventListener(Event.COMPLETE, function(event:Event):void {
                        status("loaded info for fellow traveller " + traveller.name);
                        var traveller_info:XML = XML(loader.data);
                        for each (var travellerXML:XML in traveller_info.traveller) {
                            var u:Traveller = model.getTravellerfromXML(travellerXML);
                            travellers[index] = u; // probably not necessary, but just in case
                        }
                        loadFellowTravellersInfoArray(travellers, callback, index+1);
                    }, false, 0, true);
                    var onUserInfoError:Function = function(event:Event):void {
                        error("error loading information for " + traveller.name);
                        // TODO dispatch error? retry?
                        loadFellowTravellersInfoArray(travellers, callback, index+1);
                    };                    
                    loader.addEventListener(IOErrorEvent.IO_ERROR, onUserInfoError, false, 0, true);
                    loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onUserInfoError, false, 0, true);
                }
                else {
                    loadFellowTravellersInfoArray(travellers, callback, index+1);
                }
            }
            else {
                callback(travellers);
            }
        }
        
    }
    
}